package com.dingwen.treasure.security.service;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.dingwen.treasure.base.constant.RedisConstant;
import com.dingwen.treasure.base.exception.AuthException;
import com.dingwen.treasure.common.redis.service.RedisService;
import com.dingwen.treasure.jwt.config.JwtProperties;
import com.dingwen.treasure.jwt.constant.JwtConstant;
import com.dingwen.treasure.jwt.uitl.JwtUtil;
import com.dingwen.treasure.model.vo.LoginVo;
import com.dingwen.treasure.security.dto.LoginUser;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 　TokenService: 令牌服务
 * 　@author dingwen
 * 　@date 2022/6/12
 */
@Service
@Slf4j
public class TokenService {

    /**
     * jwt 配置信息
     */
    @Resource
    private JwtProperties jwtProperties;


    /**
     * jwt 工具
     */
    @Resource
    private JwtUtil jwtUtil;

    /**
     * redis 服务
     */
    @Resource
    private RedisService redisService;


    /**
     * 通过 token 获取登录的用户信息
     *
     * @param token 令牌
     * @return
     */
    public LoginUser getLoginUser(String token) {
        // 缓存获取 +
        // redis key(USER:account)
        String key = getLoginUserKeyByToken(token);
        Object cacheObject = redisService.getCacheObject(key);
        if (!redisService.hasKey(key) || Objects.isNull(cacheObject)) {
            throw new AuthException("您的登录信息已过期，请重新登录");
        }
        return JSONUtil.toBean(cacheObject.toString(), LoginUser.class);
    }


    /**
     * 设置登录用户
     * 缓存登录用户信息： 默认5分钟
     *
     * @param loginUser 登录用户
     * @param account   账户
     */
    public void setLoginUser(LoginUser loginUser, String account) {
        String key = getLoginUserKeyByAccount(account);
        redisService.setCacheObject(key, JSONUtil.toJsonStr(loginUser), jwtProperties.getAppExpireTime(),
                TimeUnit.MINUTES);
    }

    /**
     * 设置登录vo
     *
     * @param loginVO 登录vo
     * @param account 账户
     */
    public void setLoginVO(LoginVo loginVO, String account) {
        String key = getLoginVoKeyByAccount(account);
        redisService.setCacheObject(key, JSONUtil.toJsonStr(loginVO), jwtProperties.getAppExpireTime(),
                TimeUnit.MINUTES);
    }

    /**
     * 设置登录vo
     *
     * @param account 账户
     * @return {@link LoginVo}
     */
    public LoginVo getLoginVO(String account) {
        String key = getLoginVoKeyByAccount(account);
        Object cacheObject = redisService.getCacheObject(key);
        if (!redisService.hasKey(key) || Objects.isNull(cacheObject)) {
            return null;
        }
        return JSONUtil.toBean(cacheObject.toString(), LoginVo.class);
    }

    /**
     * 通过 token 获取登录的用户信息
     *
     * @param request 请求
     * @return {@link LoginUser}
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        // token
        String token = getToken(request);
        return getLoginUser(token);
    }


    /**
     * 通过 token 获取 redis key （用户）
     *
     * @param token 令牌
     * @return {@link String}
     */
    public String getLoginUserKeyByToken(String token) {
        Claims claims = getClaims(token);
        String account = getValue(claims, JwtConstant.ACCOUNT);
        return RedisConstant.LOGIN_USER_KEY.concat(account);
    }


    /**
     * 通过 account 获取 redis key （用户）
     *
     * @param account 令牌
     * @return {@link String}
     */
    public String getLoginUserKeyByAccount(String account) {
        return RedisConstant.LOGIN_USER_KEY.concat(account);
    }


    /**
     * 通过 account 获取 redis key （用户VO）
     *
     * @param account 账号
     * @return {@link String}
     */
    public String getLoginVoKeyByAccount(String account) {
        return RedisConstant.LOGIN_VO_KEY.concat(account);
    }

    /**
     * 通过令牌获取数据声明信息
     *
     * @param token 令牌
     * @return {@link Claims}
     */
    public Claims getClaims(String token) {
        return jwtUtil.verify(token);
    }


    /**
     * 根据数据声明信息获取键值
     *
     * @param claims 数据声明信息
     * @param key    键
     * @return 值
     */
    public static String getValue(Claims claims, String key) {
        return Convert.toStr(claims.get(key), "");
    }


    /**
     * 通过请求获取令牌
     *
     * @param request 要求
     * @return {@link String}
     */
    public String getToken(HttpServletRequest request) {
        String token = request.getHeader(jwtProperties.getHeader());
        String tokenPrefix = jwtProperties.getTokenPrefix();
        if (StringUtils.isNotEmpty(token) && token.startsWith(tokenPrefix)) {
            return token.replace(tokenPrefix.concat(" "), "");
        }
        throw new AuthException("token异常，非法的请求");
    }


    /**
     * 生成web令牌
     *
     * @param loginUser 登录用户
     * @return {@link String}
     */
    public String generateWebToken(LoginUser loginUser) {
        Map<String, Object> claims = generateClaims(loginUser);
        claims.put(JwtConstant.TOKEN_TYPE, JwtConstant.WEB_TOKEN);
        String key = getTokenKeyByAccount(loginUser.getUsername());

        // 先查缓存
        String token = redisService.getCacheObject(key);
        if (StrUtil.isNotEmpty(token)) {
            jwtUtil.verify(token);
            return token;
        }

        token = generateToken(claims);
        redisService.setCacheObject(key, token, jwtProperties.getWebExpireTime(), TimeUnit.MINUTES);
        return token;
    }

    /**
     * 获取token 键
     *
     * @param account 登录账号
     * @return {@link String}
     */
    public String getTokenKeyByAccount(String account) {
        return RedisConstant.TOKEN_KEY.concat(account);
    }

    /**
     * 生成app令牌
     *
     * @param loginUser 登录用户
     * @return {@link String}
     */
    public String generateAppToken(LoginUser loginUser) {
        Map<String, Object> claims = generateClaims(loginUser);
        claims.put(JwtConstant.TOKEN_TYPE, JwtConstant.APP_TOKEN);
        String key = getTokenKeyByAccount(loginUser.getUsername());
        // 先查缓存
        String token = redisService.getCacheObject(key);
        if (StrUtil.isNotBlank(token)) {
            return token;
        }
        token = generateToken(claims);
        redisService.setCacheObject(key, token, jwtProperties.getAppExpireTime(), TimeUnit.MINUTES);
        return token;
    }

    /**
     * 生成基础数据项
     *
     * @param loginUser 登录用户
     * @return {@link Map}<{@link String}, {@link Object}>
     */
    public Map<String, Object> generateClaims(LoginUser loginUser) {
        HashMap<String, Object> claims = MapUtil.newHashMap(4);
        claims.put(JwtConstant.USER_ID, loginUser.getUserId());
        claims.put(JwtConstant.ACCOUNT, loginUser.getUser().getAccount());
        return claims;
    }

    /**
     * 生成令牌
     *
     * @param claims 声称
     * @return {@link String}
     */
    public String generateToken(Map<String, Object> claims) {
        // token
        return jwtUtil.generate(claims);
    }


    /**
     * 刷新令牌
     *
     * @param claims 数据项
     * @return {@link String}
     */
    public String refreshToken(Claims claims) {
        HashMap<String, Object> params = MapUtil.newHashMap(4);
        claims.put(JwtConstant.USER_ID, claims.get(JwtConstant.USER_ID));
        claims.put(JwtConstant.ACCOUNT, claims.get(JwtConstant.ACCOUNT));
        claims.put(JwtConstant.TOKEN_TYPE, claims.get(JwtConstant.TOKEN_TYPE));
        return generateToken(params);
    }


    /**
     * 清除登录信息
     *
     * @param account 账户
     */
    public void clearLoginInfo(String account) {
        String tokenKeyByAccount = getTokenKeyByAccount(account);
        String loginUserKeyByAccount = getLoginUserKeyByAccount(account);
        String loginVoKeyByAccount = getLoginVoKeyByAccount(account);
        redisService.deleteObject(tokenKeyByAccount);
        redisService.deleteObject(loginUserKeyByAccount);
        redisService.deleteObject(loginVoKeyByAccount);
    }
}


